Skip to content

fix: prevent DeepSeek 400 error on direct mode switch from reasoning-capable model - #994

Open
carmonium wants to merge 3 commits into
Zoo-Code-Org:mainfrom
carmonium:fix/mode-switch-reasoning-guard
Open

fix: prevent DeepSeek 400 error on direct mode switch from reasoning-capable model#994
carmonium wants to merge 3 commits into
Zoo-Code-Org:mainfrom
carmonium:fix/mode-switch-reasoning-guard

Conversation

@carmonium

@carmonium carmonium commented Jul 23, 2026

Copy link
Copy Markdown

Problem

When switching directly (without delegating via new_task) from a mode using a
reasoning-capable model (e.g., Claude Sonnet in orchestrator mode) to a mode
using a provider with strict structural expectations (e.g., DeepSeek in code
mode), the API request fails with a deterministic 400 error:

Provider Error · 400
DeepSeek completion error: 400 The `reasoning_content` in the thinking mode
must be passed back to the API.

The root cause is a three-step chain:

  1. Mode-switch bypass preserves history. SwitchModeTool.execute()
    calls ClineProvider.handleModeSwitch(), which mutates _taskMode on the
    same Task instance — no new task is created, no parentTaskId is set,
    and the entire conversation history built under the previous model is carried
    over unchanged.

  2. DeepSeek (and similar providers) require reasoning_content. When
    thinking mode is enabled, DeepSeek's API requires that every assistant
    message with tool_calls in the conversation history also include a
    reasoning_content field. The same requirement applies to Z.ai/GLM and MiMo
    providers (documented in their respective provider files).

  3. Inherited history never had reasoning_content. Messages generated by
    providers that don't use DeepSeek-style reasoning (e.g., Claude) never
    produce this field. Even if they did, buildCleanConversationHistory() in
    Task.ts strips it in the default message path.

The result is an unrecoverable error loop — the 400 is deterministic (not a
transient network failure), so automatic retries also fail.

Solution — Two-Layer Defense-in-Depth

Layer 1: Provider Guard (Unconditional Safety Net)

A new shared utility function historyHasToolCallsWithoutReasoning() detects
whether converted message history contains assistant messages with tool_calls
but no non-empty reasoning_content. It is integrated into three providers
with strict reasoning_content requirements:

  • deepseek.ts: After convertToR1Format(), computes
    effectiveThinkingEnabled = isThinkingModel && !hasIncompatibleHistory and
    uses it for all downstream thinking/effort/temperature decisions. Emits a
    provider_reasoning_guard_triggered structured log when triggered.

  • zai.ts: After convertToZAiFormat(), forces useReasoning = false
    when incompatible history is detected, disabling thinking and clearing
    reasoning_effort.

  • mimo.ts: After convertToR1Format(), switches extra_body.thinking
    from { type: "enabled" } to { type: "disabled" } when incompatible
    history is detected — the first disable path MiMo has ever had.

Layer 2: Orchestration Guard (UI Warning + Optional Auto-Condense)

A new utility modeSwitchRisksReasoningIncompatibility() checks whether a
mode switch from one provider to another risks carrying over incompatible
history (target is a strict provider, source is not).

Integrated into ClineProvider.handleModeSwitch():

  • Behavior A (always-on, default): When via !== "new_task_delegation"
    and the check is positive, emits a visible, non-interactive chat warning
    (mode_switch_compatibility_warning) informing the user that the
    conversation history may be incompatible with the new provider.

  • Behavior B (opt-in): When autoCondenseOnRiskyModeSwitch workspace
    setting is true, additionally auto-condenses the conversation via
    task.condenseContext() before the mode mutation, replacing structured
    tool_calls history with a plain-text summary.

Files Changed

New files:

  • src/api/providers/utils/reasoning-history-guard.ts — Layer 1 guard function
  • src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts — 9 tests
  • src/shared/reasoning-mode-compatibility.ts — Layer 2 utility functions
  • src/shared/__tests__/reasoning-mode-compatibility.spec.ts — 14 tests

Modified files:

  • src/api/providers/deepseek.ts — Layer 1 guard integration
  • src/api/providers/zai.ts — Layer 1 guard integration
  • src/api/providers/mimo.ts — Layer 1 guard integration (first disable path)
  • src/core/webview/ClineProvider.ts — Layer 2 guard in handleModeSwitch()
  • src/core/tools/SwitchModeTool.ts — pass via: "switch_mode" to handler
  • packages/types/src/message.ts — new clineSays value
  • webview-ui/src/components/chat/ChatRow.tsx — render new warning via WarningRow
  • webview-ui/src/i18n/locales/en/chat.json — i18n strings for warning

Testing

Unit Tests

  • reasoning-history-guard.spec.ts: 9 tests covering empty messages, tool_calls
    with/without reasoning_content, empty reasoning_content, empty tool_calls,
    non-assistant tool_calls, multi-message detection, non-array tool_calls.
  • reasoning-mode-compatibility.spec.ts: 14 tests covering strict provider
    identification, same-provider switches, non-strict→strict, strict→non-strict,
    strict↔strict, undefined (unknown) provider fail-safe behavior.

Manual Verification

  • Zero regressions on the happy path (normal, non-bypassed mode switches and
    normal thinking-mode requests are unaffected).
  • The exact incident scenario (Orchestrator → bypass switch_mode to
    Code/DeepSeek) was traced through the new code path; the original 400 error
    is now structurally prevented by Layer 1 independent of Layer 2.
  • Typecheck: clean, zero new errors.

Caveats

  • Only tested against DeepSeek, Z.ai, and MiMo providers (the three with
    documented reasoning_content strictness). Other providers with
    preserveReasoning: true (Moonshot, MiniMax, Fireworks, Bedrock,
    opencode-go) share the same structural risk but are not yet covered — they
    are tracked as a follow-up.
  • Layer 1 disables thinking mode for the affected request rather than
    synthesizing fake reasoning_content, which is a deliberate trade-off:
    deterministic safety over risk of undefined provider-side behavior.
  • Layer 2's auto-condense behavior (Behavior B) defaults to false because
    condenseContext() has real latency/token cost and a fidelity trade-off
    that should be opt-in.

Summary by CodeRabbit

  • New Features

    • Added warnings when switching modes may create provider compatibility issues.
    • Added localized chat messaging explaining risky mode switches and affected providers.
    • Automatically disables reasoning when conversation history is incompatible, helping prevent request failures.
  • Bug Fixes

    • Prevented API errors caused by tool-call history missing required reasoning details.
  • Tests

    • Added coverage for reasoning compatibility checks, mode switches, and provider-specific scenarios.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91cd999b-4e7b-41c0-b7c6-c5eb5ef93f9c

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5e564 and 9eefe93.

📒 Files selected for processing (1)
  • src/core/webview/ClineProvider.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/webview/ClineProvider.ts

📝 Walkthrough

Walkthrough

Adds reasoning-compatibility predicates and history guards for strict providers. Updates DeepSeek, MiMo, and Z.ai request behavior. Adds mode-switch checks with optional condensation. Renders localized compatibility warnings in chat.

Changes

Reasoning compatibility safeguards

Layer / File(s) Summary
Provider reasoning-history guards
src/api/providers/utils/..., src/api/providers/{deepseek,mimo,zai}.ts, src/api/providers/utils/__tests__/*
Strict-provider requests detect tool calls without reasoning_content, log warnings, and adjust reasoning fields. Tests cover supported message shapes and detection results.
Mode-switch compatibility pre-check
src/shared/reasoning-mode-compatibility.ts, src/shared/__tests__/reasoning-mode-compatibility.spec.ts, src/core/webview/ClineProvider.ts, src/core/tools/SwitchModeTool.ts
Mode switches identify their source, compare providers, emit compatibility warnings, and optionally condense task context.
Compatibility warning message flow
packages/types/src/message.ts, webview-ui/src/components/chat/ChatRow.tsx, webview-ui/src/i18n/locales/*/chat.json
Adds the warning message type and renders localized mode and provider details in chat.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: has-conflicts

Suggested reviewers: edelauna

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description thoroughly covers the problem, solution, testing, caveats, and scope, but it omits the required issue link and checklist sections. Add the approved GitHub issue reference, complete the pre-submission checklist, and document visual snapshot and documentation decisions.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix for DeepSeek errors during direct mode switches from reasoning-capable models.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/core/webview/ClineProvider.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/core/webview/ClineProvider.ts (1)

1504-1505: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant config lookups.

getModeConfigId(newMode) and listConfig() are called here and again at lines 1584-1585 for the same newMode shortly after. Consider hoisting/reusing a single lookup to avoid duplicate storage round-trips on every mode switch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1504 - 1505, In the
mode-switch flow of ClineProvider, avoid repeating the
providerSettingsManager.getModeConfigId(newMode) and listConfig() lookups
performed again near the later mode-handling block. Hoist the results or reuse
shared variables so both sections use one lookup per mode switch while
preserving the existing configuration-selection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/deepseek.ts`:
- Around line 19-20: Remove the duplicate historyHasToolCallsWithoutReasoning
import in the import section of deepseek.ts, retaining a single import from
./utils/reasoning-history-guard so compilation succeeds.

In `@src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts`:
- Around line 53-62: Rename the test case describing the empty tool_calls
scenario to state that it returns false, while preserving the existing messages
setup and historyHasToolCallsWithoutReasoning assertion.

In `@src/core/webview/ClineProvider.ts`:
- Around line 1540-1545: Fix the template literal passed to this.log in the
mode-switch compatibility check catch block by using plain backticks rather than
escaped backticks, matching the valid pattern in the nearby catch block and
restoring valid parsing.
- Around line 1501-1546: Update the compatibility pre-check in the mode-switch
flow around modeSwitchRisksReasoningIncompatibility to honor
lockApiConfigAcrossModes before resolving or comparing the target profile
provider. When the setting prevents the API profile switch, skip both the
warning and optional task.condenseContext() so checks reflect the provider that
will actually remain active; preserve existing behavior when switching is
allowed.

In `@webview-ui/src/components/chat/ChatRow.tsx`:
- Around line 1580-1599: The mode_switch_compatibility_warning branch in ChatRow
lacks local Vitest coverage. Add tests under webview-ui/src/**/__tests__ that
render ChatRow with valid warning payloads and verify the translated WarningRow
title/message, including parsed mode/provider values and fallback behavior for
missing fields; keep the existing rendering logic unchanged.

---

Nitpick comments:
In `@src/core/webview/ClineProvider.ts`:
- Around line 1504-1505: In the mode-switch flow of ClineProvider, avoid
repeating the providerSettingsManager.getModeConfigId(newMode) and listConfig()
lookups performed again near the later mode-handling block. Hoist the results or
reuse shared variables so both sections use one lookup per mode switch while
preserving the existing configuration-selection behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ceb7260c-bf05-4f55-b32b-7f780cbbda82

📥 Commits

Reviewing files that changed from the base of the PR and between 7af1a8a and 7007cf4.

📒 Files selected for processing (12)
  • packages/types/src/message.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/mimo.ts
  • src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts
  • src/api/providers/utils/reasoning-history-guard.ts
  • src/api/providers/zai.ts
  • src/core/tools/SwitchModeTool.ts
  • src/core/webview/ClineProvider.ts
  • src/shared/__tests__/reasoning-mode-compatibility.spec.ts
  • src/shared/reasoning-mode-compatibility.ts
  • webview-ui/src/components/chat/ChatRow.tsx
  • webview-ui/src/i18n/locales/en/chat.json

Comment thread src/api/providers/deepseek.ts Outdated
Comment on lines +19 to +20
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Duplicate import breaks the build.

historyHasToolCallsWithoutReasoning is imported twice from the same module. This is a duplicate identifier declaration that will fail TypeScript/esbuild compilation — matching the reported pipeline failure (esbuild.mjs --production exiting with status 1).

🐛 Proposed fix
 import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
-import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
import { historyHasToolCallsWithoutReasoning } from "./utils/reasoning-history-guard"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/deepseek.ts` around lines 19 - 20, Remove the duplicate
historyHasToolCallsWithoutReasoning import in the import section of deepseek.ts,
retaining a single import from ./utils/reasoning-history-guard so compilation
succeeds.

Source: Pipeline failures

Comment on lines +53 to +62
it("returns true when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test title contradicts its assertion.

The test is titled "returns true..." but asserts .toBe(false) — the assertion is correct (empty tool_calls shouldn't trigger the guard), but the description is inverted and will confuse future readers.

✏️ Proposed fix
-	it("returns true when assistant messages have empty tool_calls array", () => {
+	it("returns false when assistant messages have empty tool_calls array", () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("returns true when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})
it("returns false when assistant messages have empty tool_calls array", () => {
const messages = [
{
role: "assistant",
content: "hello",
tool_calls: [],
},
]
expect(historyHasToolCallsWithoutReasoning(messages)).toBe(false)
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/utils/__tests__/reasoning-history-guard.spec.ts` around
lines 53 - 62, Rename the test case describing the empty tool_calls scenario to
state that it returns false, while preserving the existing messages setup and
historyHasToolCallsWithoutReasoning assertion.

Comment on lines +1501 to +1546
if (via !== "new_task_delegation") {
try {
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName

if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)

// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compatibility pre-check ignores lockApiConfigAcrossModes.

The guard resolves toProviderName from the target mode's saved profile and can fire the warning (and optionally auto-condense context) even when the provider will never actually change, because lockApiConfigAcrossModes (checked later, at line 1577) prevents the profile switch from being applied at all. This produces spurious warnings and can trigger an unnecessary context condensation for users who lock their API config across modes.

🛠️ Proposed fix
 			if (via !== "new_task_delegation") {
 				try {
+					const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
 					const fromProviderName = task.apiConfiguration?.apiProvider
-					const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
-					const listApiConfig = await this.providerSettingsManager.listConfig()
-					const toProfile = toConfigId
-						? listApiConfig.find((c) => c.id === toConfigId)
-						: undefined
-					const toProviderName = toProfile
-						? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
-						: fromProviderName
+					const toConfigId = lockApiConfigAcrossModes
+						? undefined
+						: await this.providerSettingsManager.getModeConfigId(newMode)
+					const listApiConfig = await this.providerSettingsManager.listConfig()
+					const toProfile = toConfigId
+						? listApiConfig.find((c) => c.id === toConfigId)
+						: undefined
+					const toProviderName = toProfile
+						? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
+						: fromProviderName
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (via !== "new_task_delegation") {
try {
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName
if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)
// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}
if (via !== "new_task_delegation") {
try {
const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
const fromProviderName = task.apiConfiguration?.apiProvider
const toConfigId = lockApiConfigAcrossModes
? undefined
: await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
const toProfile = toConfigId
? listApiConfig.find((c) => c.id === toConfigId)
: undefined
const toProviderName = toProfile
? (await this.providerSettingsManager.getProfile({ name: toProfile.name })).apiProvider
: fromProviderName
if (
toProviderName &&
modeSwitchRisksReasoningIncompatibility(fromProviderName, toProviderName)
) {
// Behavior A — always show visible warning in chat
await task.say(
"mode_switch_compatibility_warning",
JSON.stringify({
fromMode,
toMode: newMode,
fromProvider: fromProviderName,
toProvider: toProviderName,
via,
}),
undefined,
undefined,
undefined,
undefined,
{ isNonInteractive: true },
)
// Behavior B — optional auto-condense when setting is enabled
const autoCondense = this.context.workspaceState.get("autoCondenseOnRiskyModeSwitch", false)
if (autoCondense) {
await task.condenseContext()
}
}
} catch (innerError) {
// Non-fatal: log but don't block the mode switch if the check fails.
this.log(
\`Mode-switch compatibility check failed: ${innerError instanceof Error ? innerError.message : String(innerError)}\`,
)
}
}
🧰 Tools
🪛 Biome (2.5.3)

[error] 1543-1543: unexpected token \

(parse)

🪛 GitHub Actions: E2E Tests (Mocked) / 0_e2e-mock.txt

[error] 1543-1543: esbuild syntax error: Syntax error "". Build failed during pnpm run bundle` (node esbuild.mjs).

🪛 GitHub Actions: E2E Tests (Mocked) / e2e-mock

[error] 1543-1543: esbuild failed with Syntax error "`" at core/webview/ClineProvider.ts:1543:7.

🪛 GitHub Actions: Release Validation / 0_validate-release.txt

[error] 1543-1543: esbuild syntax error: Syntax error "`". Build failed with 1 error.

🪛 GitHub Actions: Release Validation / validate-release

[error] 1543-1543: esbuild failed while bundling with a syntax error: "Syntax error `" (ERROR at core/webview/ClineProvider.ts:1543:7). Command: "node esbuild.mjs --production"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1501 - 1546, Update the
compatibility pre-check in the mode-switch flow around
modeSwitchRisksReasoningIncompatibility to honor lockApiConfigAcrossModes before
resolving or comparing the target profile provider. When the setting prevents
the API profile switch, skip both the warning and optional
task.condenseContext() so checks reflect the provider that will actually remain
active; preserve existing behavior when switching is allowed.

Comment thread src/core/webview/ClineProvider.ts
Comment on lines +1580 to +1599
case "mode_switch_compatibility_warning": {
const warningData = safeJsonParse<{
fromMode?: string
toMode?: string
fromProvider?: string
toProvider?: string
via?: string
}>(message.text || "{}")
if (!warningData) return null
return (
<WarningRow
title={t("chat:modeSwitchCompatibilityWarning.title")}
message={t("chat:modeSwitchCompatibilityWarning.message", {
fromMode: warningData.fromMode ?? "?",
toMode: warningData.toMode ?? "?",
toProvider: warningData.toProvider ?? "?",
})}
/>
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rendering logic is correct; add local test coverage.

The parsing/rendering itself matches the established pattern and WarningRowProps contract. No Vitest coverage for this new mode_switch_compatibility_warning branch is included in this batch.

As per coding guidelines, "Prefer local webview-ui tests for React/webview behavior such as component rendering... Add or update Vitest coverage under webview-ui/src/**/__tests__ instead of reaching for apps/vscode-e2e."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webview-ui/src/components/chat/ChatRow.tsx` around lines 1580 - 1599, The
mode_switch_compatibility_warning branch in ChatRow lacks local Vitest coverage.
Add tests under webview-ui/src/**/__tests__ that render ChatRow with valid
warning payloads and verify the translated WarningRow title/message, including
parsed mode/provider values and fallback behavior for missing fields; keep the
existing rendering logic unchanged.

Source: Coding guidelines

@coderabbitai coderabbitai Bot mentioned this pull request Jul 31, 2026
7 tasks
@github-actions github-actions Bot added the has-conflicts PR has merge conflicts with the base branch label Aug 3, 2026
@carmonium
carmonium force-pushed the fix/mode-switch-reasoning-guard branch from 7007cf4 to 9f5e564 Compare August 3, 2026 22:41
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot removed the has-conflicts PR has merge conflicts with the base branch label Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant